package model

import (
	j_log "config-client-v1/init/j-log"
	"config-client-v1/utils/tools"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"
)

type Frame struct {
	ID               int               `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
	Code1            string            `gorm:"column:code1" json:"code1"`
	Code2            string            `gorm:"column:code2" json:"code2"`
	Code3            string            `gorm:"column:code3" json:"code3"`
	ProjectID        int               `gorm:"column:project_id" json:"project_id"`               // 工程ID
	PPEID            int               `gorm:"column:ppe_id" json:"ppe_id"`                       // PPEID
	Name             string            `gorm:"column:name" json:"name"`                           // 帧名称
	FrameType        string            `gorm:"column:frame_type" json:"frame_type"`               // 帧类型[send,recv,read,write], 其中read,write为modbus专用
	FrameMode        string            `gorm:"column:frame_mode" json:"frame_mode"`               // (CAN)帧类型[std,ext] 标准帧, 扩展帧
	FrameFormat      string            `gorm:"column:frame_format" json:"frame_format"`           // (CAN)帧格式[data,remote] 数据帧, 远程帧
	FrameAddr        string            `gorm:"column:frame_addr" json:"frame_addr"`               // (CAN)帧ID,0x7ff,0x1fffffff
	DecType          string            `gorm:"column:dec_type" json:"dec_type"`                   // 进制类型, 默认HEX,暂时别管其他的
	Length           int               `gorm:"column:length" json:"length"`                       // 帧长度
	SendType         uint8             `gorm:"column:send_type" json:"send_type"`                 // 发送方式 [1.循环发送 2.条件触发 3.事件触发]
	ScanRate         int               `gorm:"column:scan_rate" json:"scan_rate"`                 // 发送帧 发送间隔(单位ms)
	SlaveID          uint8             `gorm:"column:slave_id" json:"slave_id"`                   // 从站ID(modbus)
	FunctionCode     string            `gorm:"column:function_code" json:"function_code"`         // 功能码(modbus)
	DBNumber         int               `gorm:"column:db_number" json:"db_number"`                 // DB号(PLC)
	StartAddr        uint16            `gorm:"column:start_addr" json:"start_addr"`               // 寄存器、线圈起始地址(modbus)
	Quantity         int               `gorm:"column:quantity" json:"quantity"`                   // 寄存器、线圈个数(modbus)
	DataMode         uint8             `gorm:"column:data_mode" json:"data_mode"`                 // 数据模式 [1.自定义 2.手动输入]
	JsonDetail       string            `gorm:"column:json_detail" json:"json_detail"`             // 帧结构内容, 实际内容为 []*FrameItem 的json字符串, 只是为了将帧结构和变量区分开来.
	DataContent      string            `gorm:"column:data_content" json:"data_content"`           // 帧数据原始内容, 十六进制字符串
	TriggerCondition string            `gorm:"column:trigger_condition" json:"trigger_condition"` // 触发条件
	TriggerRelation  string            `gorm:"column:trigger_relation" json:"trigger_relation"`   // 触发条件关系 [AND,OR]
	TriggerInterval  int               `gorm:"column:trigger_interval" json:"trigger_interval"`   // 触发间隔(单位ms), 默认5秒
	Status           uint8             `gorm:"column:status" json:"status"`                       // 1.启用 2.停用
	TriggerVar       string            `gorm:"column:trigger_var" json:"trigger_var"`             // 触发变量(已作废)
	TriggerType      string            `gorm:"column:trigger_type" json:"trigger_type"`           // 触发方式(已作废)
	RelatedFrameID   int               `gorm:"column:related_frame_id" json:"related_frame_id"`   // 关联帧ID
	CreateTime       *string           `gorm:"column:create_time" json:"create_time"`
	UpdateTime       *string           `gorm:"column:update_time" json:"update_time"`
	FrameItemParam   []*FrameItemParam `json:"frame_item_param"  gorm:"-"`
	FrameItem        []*FrameItem      `json:"frame_item"  gorm:"foreignKey:FrameID;references:ID"` // has many 模型
	LastTriggerTime  time.Time         `json:"-" gorm:"-"`
}

type FrameSlim struct {
	ID        int    `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
	ProjectID int    `gorm:"column:project_id" json:"project_id"`            // 工程ID
	PPEID     int    `gorm:"column:ppe_id" json:"ppe_id"`                    // PPEID
	Name      string `gorm:"column:name" json:"name"`                        // 帧名称
	Status    uint8  `gorm:"column:status" json:"status"`                    // 1.启用 2.停用
	PPEInfo   *PPE   `json:"ppe_info" gorm:"foreignKey:PPEID;references:ID"` // 设备信息
}

func (*Frame) TableName() string {
	return "frame"
}

func (*FrameSlim) TableName() string {
	return "frame"
}

func (frame *Frame) Exists(id int) (exist bool, err error) {
	err = DB.Where("id = ?", id).Limit(1).Find(frame).Error
	if err != nil {
		return
	}
	if frame.ID == 0 {
		return
	}
	exist = true
	return
}

// FrameItem 变量和帧字段共用结构
type FrameItem struct {
	ID            int     `json:"id" gorm:"column:id"`
	Code1         string  `json:"code1" gorm:"column:code1"`
	Code2         string  `json:"code2" gorm:"column:code2"`
	Code3         string  `json:"code3" gorm:"column:code3"`
	Code4         string  `json:"code4" gorm:"column:code4"`
	ProjectID     int     `gorm:"column:project_id" json:"project_id"`         // 工程ID
	PPEID         int     `gorm:"column:ppe_id" json:"ppe_id"`                 // 协议ID
	FrameID       int     `gorm:"column:frame_id" json:"frame_id"`             // 帧ID
	FSID          int     `gorm:"column:fs_id" json:"fs_id"`                   // 自研设备ID
	ModuleID      int     `gorm:"column:module_id" json:"module_id"`           // 模块ID
	PID           int     `gorm:"column:pid" json:"pid"`                       // 自研设备虚拟变量PID
	Addr          string  `json:"addr" gorm:"column:addr"`                     // 变量地址
	FieldName     string  `json:"field_name" gorm:"column:field_name"`         // 变量名 (当type是变量时设置)
	SpecType      string  `json:"spec_type" gorm:"column:spec_type"`           // 数据解析类型 (uint8, uint16, uint32, float32, bool) // 具体类型根据粗类型和起始字节长度计算出
	RealSpecType  string  `json:"real_spec_type" gorm:"column:real_spec_type"` // 真类型 (用于存储前强制转换)
	Value         float64 `json:"value" gorm:"column:value"`                   // 解析值
	EV            uint8   `json:"ev" gorm:"column:ev"`                         // 有效值(开关类布尔值专用)
	Endian        string  `json:"endian" gorm:"column:endian"`                 // 大小端 [big,little]
	RatioOperator string  `json:"ratio_operator" gorm:"column:ratio_operator"` // 系数运算符 [*,/]
	Ratio         float64 `json:"ratio" gorm:"column:ratio"`                   // 系数 value除之后的值
	Fe            uint8   `gorm:"column:fe" json:"fe"`                         // 是否科学计数法0.否 1.是
	Precision     uint8   `gorm:"column:precision" json:"precision"`           // 小数点保留几位

	UpperLimitVarAddr  string  `gorm:"column:upper_limit_var_addr" json:"upper_limit_var_addr"`   // 上限值-变量地址 (0.未使用变量)
	UpperLimitValue    float64 `gorm:"column:upper_limit_value" json:"upper_limit_value"`         // 上限值-数值
	UpperLimitEnable   uint8   `gorm:"column:upper_limit_enable" json:"upper_limit_enable"`       // 是否启用 0.不启用 1.启用
	Upper2LimitVarAddr string  `gorm:"column:upper2_limit_var_addr" json:"upper2_limit_var_addr"` // 上上限值-变量地址
	Upper2LimitValue   float64 `gorm:"column:upper2_limit_value" json:"upper2_limit_value"`       // 上上限值-数值
	Upper2LimitEnable  uint8   `gorm:"column:upper2_limit_enable" json:"upper2_limit_enable"`     // 是否启用 0.不启用 1.启用
	LowerLimitVarAddr  string  `gorm:"column:lower_limit_var_addr" json:"lower_limit_var_addr"`   // 下限值-变量地址
	LowerLimitValue    float64 `gorm:"column:lower_limit_value" json:"lower_limit_value"`         // 下限值-数值
	LowerLimitEnable   uint8   `gorm:"column:lower_limit_enable" json:"lower_limit_enable"`       // 是否启用 0.不启用 1.启用
	Lower2LimitVarAddr string  `gorm:"column:lower2_limit_var_addr" json:"lower2_limit_var_addr"` // 下下限值-变量地址
	Lower2LimitValue   float64 `gorm:"column:lower2_limit_value" json:"lower2_limit_value"`       // 下下限值-数值
	Lower2LimitEnable  uint8   `gorm:"column:lower2_limit_enable" json:"lower2_limit_enable"`     // 是否启用 0.不启用 1.启用

	ShieldVarAddr string `gorm:"column:shield_var_addr" json:"shield_var_addr"` // 屏蔽-变量地址
	Shield        uint8  `gorm:"column:shield" json:"shield"`                   // 屏蔽/滤波/脉冲 0.否 1.是
	ShieldTime    int    `gorm:"column:shield_time" json:"shield_time"`         // 滤波/脉冲 持续时间(ms)

	ChannelType     string  `gorm:"column:channel_type" json:"channel_type"`           // Other模块的通道类型 AI,AQ,DI,DQ
	ValueType       string  `gorm:"value_type" json:"value_type"`                      // 变量类型(压力,温度)
	IsInternal      uint8   `gorm:"column:is_internal" json:"is_internal"`             // 是否内部变量
	RangeUpperLimit float64 `gorm:"column:range_upper_limit" json:"range_upper_limit"` // 量程转换上限
	RangeLowerLimit float64 `gorm:"column:range_lower_limit" json:"range_lower_limit"` // 量程转换下限
	RCFormulaID     int     `gorm:"column:rc_formula_id" json:"rc_formula_id"`         // 量程转换公式ID
	Formula         uint8   `gorm:"column:formula" json:"formula"`                     // 计算公式

	AlertVarAddr string `gorm:"column:alert_var_addr" json:"alert_var_addr"` // 报警变量地址
	AlertBit     uint8  `gorm:"column:alert_bit" json:"alert_bit"`           // 报警位-数值
	DiagMode     uint8  `gorm:"column:diag_mode" json:"diag_mode"`           // 诊断模式
	AlertType    uint8  `gorm:"column:alert_type" json:"alert_type"`         // 报警类型
	StoreRate    int    `gorm:"column:store_rate" json:"store_rate"`         // 存储周期(单位: ms)
	Desc         string `gorm:"column:desc" json:"desc"`                     // 描述备注

	CreateTime *string `gorm:"column:create_time" json:"create_time"`
	UpdateTime *string `gorm:"column:update_time" json:"update_time"`
	OPCAddr    string  `gorm:"column:opc_addr" json:"opc_addr"`
}

// FrameItemParam 帧内容字段 等同于变量的部分字段
type FrameItemParam struct {
	FieldName     string  `json:"field_name" gorm:"column:field_name"`         // 字段名,变量名 只是个名字, 示例: 风速,压力
	Type          string  `json:"type" gorm:"column:type"`                     // 数据块类型, 可选范围 [包头, 帧长, 地址, 功能码, 数据部分, 校验, 帧尾]
	InputMethod   uint8   `json:"input_method" gorm:"column:input_method"`     // 方式 可选[0.直接输入 1.设置变量]
	Start         int     `json:"start" gorm:"column:start"`                   // 开始字节
	End           int     `json:"end" gorm:"column:end"`                       // 结束字节
	RawData       string  `json:"raw_data" gorm:"column:raw_data"`             // 原始十六进制字符串 (type为非"数据部分"和"校验" 时都是直接输入) (type为"校验"时自动计算, type为"数据部分"时按下面规则解析)
	CheckMethod   string  `json:"check_method" gorm:"column:check_method"`     // 校验方式 可选[ crc16 ,sum ] (当type是校验时设置)
	CheckStart    int     `json:"check_start" gorm:"column:check_start"`       // 校验开始位置   (当type是校验时设置)
	CheckEnd      int     `json:"check_end" gorm:"column:check_end"`           // 校验结束位置   (当type是校验时设置)
	Addr          string  `json:"addr" gorm:"column:addr"`                     // 设置的变量地址  (当input_type为1时设置, 也可以不设置, 如果不设置将自动创建变量)
	SpecType      string  `json:"spec_type" gorm:"column:spec_type"`           // 数据解析类型 (uint8, uint16, uint32, float32, float64, bool) ,具体类型根据粗类型和起始字节长度计算出
	RealSpecType  string  `json:"real_spec_type" gorm:"column:real_spec_type"` // 真类型
	Value         float64 `json:"value" gorm:"column:value"`                   // 解析值 (发送帧时可用)
	BoolBits      uint8   `json:"bool_bits" gorm:"column:bool_bits"`           // 多字节时的第几位
	Endian        string  `json:"endian" gorm:"column:endian"`                 // 大小端 [big,little]
	RatioOperator string  `json:"ratio_operator" gorm:"column:ratio_operator"` // 系数运算符 [*,/]
	Ratio         float64 `json:"ratio" gorm:"column:ratio"`                   // 系数 value 除之后的值
	Fe            uint8   `gorm:"column:fe" json:"fe"`                         // 是否科学计数法0.否 1.是
	Precision     uint8   `gorm:"column:precision" json:"precision"`           // 小数点保留几位
	MultiByte     uint8   `json:"multi_byte" gorm:"column:multi_byte"`         // data_type 为 bool 时 0.单字节 1.多字节
	//CreateTime  *string `json:"create_time" gorm:"create_time"`
	//UpdateTime  *string `json:"update_time" gorm:"update_time"`
}

func (*FrameItem) TableName() string {
	return "frame_item"
}

type OPCServerItem struct {
	ID        int    `json:"id" gorm:"column:id"`
	ProjectID int    `gorm:"column:project_id" json:"project_id"`
	PPEID     int    `gorm:"column:ppe_id" json:"ppe_id"`
	PAddr     string `gorm:"column:paddr" json:"paddr"`
	Tag       string `json:"tag" gorm:"column:tag"`
}

func (*OPCServerItem) TableName() string {
	return "opc_server_item"
}

func (frame *Frame) Send() (err error, rawData []byte) {
	if frame.FrameType != "send" && frame.FrameType != "write" {
		err = errors.New(fmt.Sprintf("frame_name=%s, 当前变量不属于发送帧", frame.Name))
		return
	}
	ppe, ok := RunningPPEMap.Get(frame.PPEID)
	if !ok {
		err = errors.New(fmt.Sprintf("frame_name=%s, 帧所属第三方设备未运行", frame.Name))
		return
	}
	list := make([]*FrameItemParam, 0)
	err = json.Unmarshal([]byte(frame.JsonDetail), &list)
	if err != nil {
		err = errors.New(fmt.Sprintf("frame_name=%s, 帧解析JSON失败; %s", frame.Name, err.Error()))
		return
	}
	data, err := CreateFrameRawData(list)
	if err != nil {
		err = errors.New(fmt.Sprintf("frame_name=%s, 帧生成原始数据失败; %s", frame.Name, err.Error()))
		return
	}
	rawData = data
	switch ppe.ProtocolName {
	case "RS485":
		if ppe.RS485Client == nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, 串口状态异常 nil", frame.Name))
			return
		}
		port := *(ppe.RS485Client)
		_, err = port.Write(data)
		if err != nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, 串口写入失败 %s", frame.Name, err.Error()))
			return
		}
	case "TCP/IP":
		if ppe.Mode == "client" {
			if ppe.TcpClient == nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, TCP/IP client异常 nil", frame.Name))
				return
			}
			_, err = ppe.TcpClient.Connection.Write(data)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, TCP/IP client写入失败 %s", frame.Name, err.Error()))
				return
			}
		} else {
			if ppe.TCPOnlineMap == nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, TCP/IP server异常 nil", frame.Name))
				return
			}
			totalN, successN, err2 := ppe.TcpServerMultiWrite(data)
			if err2 != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, TcpServerMultiWrite写入失败,totalN=%d,successN=%d,%s", frame.Name, totalN, successN, err2.Error()))
				return
			}
		}
	case "MODBUS TCP":
		if ppe.Mode != "client" {
			err = errors.New(fmt.Sprintf("frame_name=%s, 模式异常,请检查", frame.Name))
			return
		}
		if ppe.ModbusTCPClient == nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, modbus tcp状态异常 nil", frame.Name))
			return
		}
		err = ppe.ModbusTCPClient.SetUnitId(frame.SlaveID)
		if err != nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, modbus设置从站失败, %s", frame.Name, err.Error()))
			return
		}
		startAddr := frame.StartAddr
		switch frame.FunctionCode {
		case "05":
			dataCoil, err2 := CreateFrameCoilsRawData(list)
			if err2 != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, 生成modbus帧原始数据失败; %s", frame.Name, err2.Error()))
				return
			}
			if frame.Quantity != 1 || len(dataCoil) != 1 {
				err = errors.New(fmt.Sprintf("frame_name=%s, 0x05写入单线圈个数必须为1", frame.Name))
				return
			}
			err = ppe.ModbusTCPClient.WriteCoil(startAddr, dataCoil[0])
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, WriteCoil失败 %s", frame.Name, err.Error()))
				ppe.RunningState = 3
				return
			}
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s] 0x05写入线圈成功", ppe.Name, frame.Name)})
		case "06":
			if len(data) != frame.Quantity*2 {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入数据长度不符", frame.Name))
				return
			}
			if len(data) != 2 {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入单个寄存器长度不符, len=%d,quantity=%d", frame.Name, len(data), frame.Quantity))
				return
			}
			err = ppe.ModbusTCPClient.WriteRegister(startAddr, tools.BytesToUint16(data, "big"))
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, WriteRegister失败 %s", frame.Name, err.Error()))
				ppe.RunningState = 3
				return
			}
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s] 0x06写入单个寄存器成功", ppe.Name, frame.Name)})
		case "0F":
			dataCoil, err2 := CreateFrameCoilsRawData(list)
			if err2 != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, 生成modbus帧原始数据失败; %s", frame.Name, err2.Error()))
				return
			}
			if frame.Quantity != len(dataCoil) {
				err = errors.New(fmt.Sprintf("frame_name=%s, 0x0F写入多线圈个数不匹配", frame.Name))
				return
			}
			err = ppe.ModbusTCPClient.WriteCoils(startAddr, dataCoil)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, WriteCoils失败 %s", frame.Name, err.Error()))
				ppe.RunningState = 3
				return
			}
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s] 0x0F写入线圈成功", ppe.Name, frame.Name)})
		case "10":
			if len(data) != frame.Quantity*2 {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入数据长度不符", frame.Name))
				return
			}
			err = ppe.ModbusTCPClient.WriteBytes(startAddr, data)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入寄存器失败; %s", frame.Name, err.Error()))
				ppe.RunningState = 3
				return
			}
		}
	case "MODBUS RTU":
		if ppe.ModbusRTUClient == nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, modbus rtu状态异常 nil", frame.Name))
			return
		}
		err = ppe.ModbusRTUClient.SetUnitId(frame.SlaveID)
		if err != nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, modbus设置从站失败, %s", frame.Name, err.Error()))
			return
		}
		startAddr := frame.StartAddr
		switch frame.FunctionCode {
		case "05":
			dataCoil, err2 := CreateFrameCoilsRawData(list)
			if err2 != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, 生成modbus帧原始数据失败; %s", frame.Name, err2.Error()))
				return
			}
			if frame.Quantity != 1 || len(dataCoil) != 1 {
				err = errors.New(fmt.Sprintf("frame_name=%s, 0x05写入单线圈个数必须为1", frame.Name))
				return
			}
			err = ppe.ModbusRTUClient.WriteCoil(startAddr, dataCoil[0])
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, WriteCoil失败 %s", frame.Name, err.Error()))
				return
			}
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s] 0x05写入线圈成功", ppe.Name, frame.Name)})
		case "06":
			if len(data) != frame.Quantity*2 {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入数据长度不符", frame.Name))
				return
			}
			if len(data) != 2 {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入单个寄存器长度不符, len=%d,quantity=%d", frame.Name, len(data), frame.Quantity))
				return
			}
			err = ppe.ModbusRTUClient.WriteRegister(startAddr, tools.BytesToUint16(data, "big"))
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, WriteRegister失败 %s", frame.Name, err.Error()))
				return
			}
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s] 0x06写入单个寄存器成功", ppe.Name, frame.Name)})
		case "0F":
			dataCoil, err2 := CreateFrameCoilsRawData(list)
			if err2 != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, 生成modbus帧原始数据失败; %s", frame.Name, err2.Error()))
				return
			}
			if frame.Quantity != len(dataCoil) {
				err = errors.New(fmt.Sprintf("frame_name=%s, 0x0F写入多线圈个数不匹配", frame.Name))
				return
			}
			err = ppe.ModbusRTUClient.WriteCoils(startAddr, dataCoil)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, WriteCoils失败 %s", frame.Name, err.Error()))
				return
			}
			j_log.LogInfo([]interface{}{fmt.Sprintf("[%s][%s] 0x0F写入线圈成功", ppe.Name, frame.Name)})
		case "10":
			if len(data) != frame.Quantity*2 {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入数据长度不符", frame.Name))
				return
			}
			err = ppe.ModbusRTUClient.WriteBytes(startAddr, data)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus写入寄存器失败; %s", frame.Name, err.Error()))
				return
			}
		}
	case "CAN2.0B":
		if frame.DataMode != 1 {
			data, err = tools.HexStrToByteArray(frame.DataContent)
			err = errors.New(fmt.Sprintf("frame_name=%s, CAN2.0B模式下, 帧生成原始数据失败; %s", frame.Name, err.Error()))
			return
		}
		fullData, err2 := CreateCanFrame(frame, data)
		if err2 != nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, 发送帧生成CAN帧失败; %s", frame.Name, err2.Error()))
			return
		}
		_, err = ppe.TcpClient.Connection.Write(fullData)
		if err != nil {
			err = errors.New(fmt.Sprintf("can帧tcp写入失败; %s", err.Error()))
			return
		}
	case "PLC":
		config := PLCConfig{}
		err = json.Unmarshal([]byte(ppe.Config), &config)
		if err != nil {
			err = errors.New(fmt.Sprintf("frame_name=%s, PLC配置解析失败; %s", frame.Name, err.Error()))
			return
		}
		if config.Type == "S7-1200" || config.Type == "S7-1500" {
			if len(data) != frame.Quantity {
				err = errors.New(fmt.Sprintf("frame_name=%s, PLC写入的点位个数与帧字节长度不符;quantity=%d, data=%v", frame.Name, frame.Quantity, data))
				return
			}
			err = (*ppe.PLCSnap7Client).AGWriteDB(frame.DBNumber, int(frame.StartAddr), frame.Quantity, data)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, PLC写入失败; %s", frame.Name, err.Error()))
				return
			}
		} else {
			err = ppe.ModbusTCPClient.SetUnitId(frame.SlaveID)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, modbus设置从站失败, %s", frame.Name, err.Error()))
				return
			}
			startAddr := frame.StartAddr
			if len(data) != frame.Quantity*2 {
				err = errors.New(fmt.Sprintf("[%s][%s]写入的寄存器个数与原始数据字节数不符;len=%d ,quantity=%d", ppe.Name, frame.Name, len(data), frame.Quantity))
				return
			}
			err = ppe.ModbusTCPClient.WriteBytes(startAddr, data)
			if err != nil {
				err = errors.New(fmt.Sprintf("frame_name=%s, ppe.ModbusTCPClient.WriteBytes失败 %s", frame.Name, err.Error()))
				return
			}
		}
	default:
		err = errors.New(fmt.Sprintf("暂不支持此协议发送帧 %s", ppe.ProtocolName))
		return
	}
	return
}

func RelationFrameTrigger(RelatedFrameID int) (err error) {
	frame := &Frame{}
	err = DB.Where("id = ?", RelatedFrameID).First(frame).Error
	if err != nil {
		return
	}
	err, _ = frame.Send()
	return
}

/*--- 帧参数验证 ---*/

// GetAddrsFromFrameParam 找出帧参数中已设置的变量地址
func GetAddrsFromFrameParam(list []*FrameItemParam) (addrs []string) {
	addrs = make([]string, 0)
	for _, row := range list {
		if row.Addr != "" {
			addrs = append(addrs, row.Addr)
		}
	}
	return
}

// GetVarQuantityFromFrameParam 找出帧参数中变量数量
func GetVarQuantityFromFrameParam(list []*FrameItemParam) (quantity int) {
	quantity = 0
	for _, row := range list {
		if row.InputMethod == 1 {
			quantity++
		}
	}
	return
}

func GetVariablesMapByAddrs(projectID int, addrs []string) (m map[string]*FrameItem, err error) {
	m = make(map[string]*FrameItem)
	checkList := make([]*FrameItem, 0) // 找出所有已设置变量地址对应的变量, 和帧类型做对比用
	if len(addrs) > 0 {
		err = DB.Where("project_id = ? AND addr in ?", projectID, addrs).Find(&checkList).Error
		if err != nil {
			return
		}
	}
	for _, row := range checkList {
		m[row.Addr] = row
	}
	return
}

// ValidateFrameV2 验证帧参数完整版, 上面接口升级版
func (frame *Frame) ValidateFrameV2(ppe *PPE) (err error) {
	if frame.Name == "" {
		return errors.New(fmt.Sprintf("帧名称不能为空"))
	}
	if frame.ScanRate < 1 {
		frame.ScanRate = 1
	}
	list := frame.FrameItemParam
	/*--- 验证帧基本参数 ---*/
	switch ppe.ProtocolName {
	case "CAN2.0B":
		_, ok := AllowFrameTypeRS485[frame.FrameType]
		if !ok {
			return errors.New(fmt.Sprintf("%s协议可选发送帧类型为[send,recv]", ppe.ProtocolName))
		}
		if frame.FrameMode != "std" && frame.FrameMode != "ext" {
			return errors.New(fmt.Sprintf("%s协议可选扩展帧类型为[std,ext]", ppe.ProtocolName))
		}
		if frame.FrameFormat != "data" && frame.FrameFormat != "remote" {
			return errors.New(fmt.Sprintf("%s协议可选帧格式为[data,remote]", ppe.ProtocolName))
		}
		if frame.FrameAddr == "" {
			return errors.New(fmt.Sprintf("%s协议帧ID不能为空", ppe.ProtocolName))
		}
		frame.FrameAddr, err = canFrameAddrHandle(frame.FrameMode, frame.FrameAddr)
		if err != nil {
			return
		}
		if frame.Length < 0 || frame.Length > 8 {
			return errors.New(fmt.Sprintf("%s协议帧长度可选范围[0,8]", ppe.ProtocolName))
		}
		for _, item := range list {
			item.InputMethod = 1
		}
		if frame.DataMode == 2 {
			byteArr, err2 := tools.HexStrToByteArray(frame.DataContent)
			if err2 != nil {
				err = errors.New(fmt.Sprintf("手输帧内容不是有效的16进制字符串"))
				return
			}
			if len(byteArr) != frame.Length {
				return errors.New(fmt.Sprintf("%s手输帧内容字节长度与设置长度不一致", ppe.Name))
			}
			frame.FrameItem = make([]*FrameItem, 0)
		}
	case "RS232":
	case "RS485":
		_, ok := AllowFrameTypeRS485[frame.FrameType]
		if !ok {
			return errors.New(fmt.Sprintf("%s协议可选帧类型为[send,recv]", ppe.ProtocolName))
		}
	case "MODBUS RTU":
		fallthrough
	case "MODBUS TCP":
		if frame.SlaveID == 0 {
			return errors.New(fmt.Sprintf("%s协议需设置从站地址", ppe.ProtocolName))
		}
		if frame.FunctionCode == "" {
			return errors.New(fmt.Sprintf("%s协议需设置功能码", ppe.ProtocolName))
		}
		_, err = tools.ValidateHexAndLen(frame.FunctionCode, 1)
		if err != nil {
			return errors.New(fmt.Sprintf("%s功能码格式错误", ppe.ProtocolName))
		}
		switch frame.FunctionCode {
		case "01":
			fallthrough
		case "02":
			if frame.Quantity == 0 {
				return errors.New(fmt.Sprintf("请设置读取线圈数量"))
			}
			frame.FrameType = "read"
			frame.Length = frame.Quantity * 1
			for _, row := range frame.FrameItemParam {
				row.SpecType = "bool"
				row.Endian = "big"
			}
		case "03":
			fallthrough
		case "04":
			if frame.Quantity == 0 {
				return errors.New(fmt.Sprintf("请设置读取寄存器数量"))
			}
			frame.FrameType = "read"
			frame.Length = frame.Quantity * 2
		case "0F":
			if frame.Quantity == 0 {
				return errors.New(fmt.Sprintf("请设置写入线圈数量"))
			}
			frame.FrameType = "write"
			frame.Length = frame.Quantity
			for _, row := range frame.FrameItemParam {
				row.SpecType = "bool"
				row.Endian = "big"
			}
		case "10":
			if frame.Quantity == 0 {
				return errors.New(fmt.Sprintf("请设置写入寄存器数量"))
			}
			frame.FrameType = "write"
			frame.Length = frame.Quantity * 2
		case "05":
			if frame.Quantity != 1 {
				return errors.New(fmt.Sprintf("线圈数量必须为1"))
			}
			frame.FrameType = "write"
			frame.Length = frame.Quantity
			for _, row := range frame.FrameItemParam {
				row.SpecType = "bool"
				row.Endian = "big"
			}
		case "06":
			if frame.Quantity != 1 {
				return errors.New(fmt.Sprintf("寄存器数量必须为1"))
			}
			frame.FrameType = "write"
			frame.Length = frame.Quantity * 2
		default:
			return errors.New(fmt.Sprintf("%s暂不支持功能码%s", ppe.ProtocolName, frame.FunctionCode))
		}

		for _, row := range frame.FrameItemParam {
			row.InputMethod = 1
		}
		if frame.FunctionCode == "01" || frame.FunctionCode == "02" || frame.FunctionCode == "05" || frame.FunctionCode == "0F" {
			if (frame.FunctionCode == "05" || frame.FunctionCode == "0F") && len(list) != frame.Quantity {
				return errors.New(fmt.Sprintf("modbus功能码%s 变量数量与读写的线圈数量不匹配", frame.FunctionCode))
			}
			allBoolean := true
			for _, row := range list {
				if row.SpecType != "bool" {
					allBoolean = false
					break
				}
				row.End = row.Start
			}
			if !allBoolean {
				return errors.New(fmt.Sprintf("modbus功能码%s只支持布尔变量", frame.FunctionCode))
			}
		}
	case "TCP/IP":
		_, ok := AllowFrameTypeRS485[frame.FrameType]
		if !ok {
			return errors.New(fmt.Sprintf("%s协议可选帧类型为[send,recv]", ppe.ProtocolName))
		}
	case "PLC":
		_, ok := AllowFrameTypeModbus[frame.FrameType]
		if !ok {
			return errors.New(fmt.Sprintf("%s协议可选帧类型为[read,write]", ppe.ProtocolName))
		}
		for _, item := range frame.FrameItemParam {
			item.InputMethod = 1
		}
		switch ppe.Mode {
		case "S7-1200":
			fallthrough
		case "S7-1500":
			if frame.DBNumber == 0 {
				return errors.New(fmt.Sprintf("DB号不可为空"))
			}
			if frame.Quantity == 0 {
				return errors.New(fmt.Sprintf("请设置读取点位数量"))
			}
			frame.Length = frame.Quantity
		case "汇川":
			if frame.SlaveID == 0 {
				return errors.New(fmt.Sprintf("%s:%s协议需设置从站地址", ppe.ProtocolName, ppe.Mode))
			}
			if frame.FunctionCode == "" {
				return errors.New(fmt.Sprintf("%s:%s协议需设置功能码", ppe.ProtocolName, ppe.Mode))
			}
			_, err = tools.ValidateHexAndLen(frame.FunctionCode, 1)
			if err != nil {
				return errors.New(fmt.Sprintf("%s:%s功能码格式错误", ppe.ProtocolName, ppe.Mode))
			}
			switch frame.FunctionCode {
			case "03":
				fallthrough
			case "04":
				if frame.Quantity == 0 {
					return errors.New(fmt.Sprintf("请设置读取寄存器数量"))
				}
				frame.FrameType = "read"
				frame.Length = frame.Quantity * 2
			case "10":
				if frame.Quantity == 0 {
					return errors.New(fmt.Sprintf("请设置写入寄存器数量"))
				}
				frame.FrameType = "write"
				frame.Length = frame.Quantity * 2
			case "06":
				if frame.Quantity != 1 {
					return errors.New(fmt.Sprintf("寄存器数量必须为1"))
				}
				frame.FrameType = "write"
				frame.Length = frame.Quantity * 2
			default:
				return errors.New(fmt.Sprintf("%s:汇川暂不支持功能码%s", ppe.ProtocolName, frame.FunctionCode))
			}
		default:
			return errors.New(fmt.Sprintf("未知的PLC型号"))
		}
	default:
		j_log.LogError([]interface{}{ppe.Name, "暂不支持此协议", ppe.ProtocolName})
		return errors.New("暂不支持此协议" + ppe.ProtocolName)
	}
	/*--- 验证通用帧内容 ---*/
	if ppe.ProtocolName != "CAN2.0B" && frame.Length == 0 {
		return errors.New(fmt.Sprintf("帧长必须大于0"))
	}
	if frame.FrameType == "send" || frame.FrameType == "write" {
		if !(frame.SendType >= 1 && frame.SendType <= 3) {
			return errors.New(fmt.Sprintf("请选择发送方式"))
		}
		if len(list) > 0 {
			if list[0].Start != 0 {
				err = errors.New(fmt.Sprintf("[%s]发送帧占用字节需完全覆盖帧长,首个字段start必须为0", frame.Name))
				return
			}
			if list[len(list)-1].End != frame.Length-1 {
				err = errors.New(fmt.Sprintf("[%s]发送帧占用字节需完全覆盖帧长,结尾字段end值必须等于帧长-1", frame.Name))
				return
			}
		}
		for i := 0; i < len(list); i++ {
			row := list[i]
			if row.MultiByte == 1 {
				err = errors.New(fmt.Sprintf("[%s][%s] 发送帧不能包含多字节布尔值", frame.Name, row.FieldName))
				return
			}
			if i > 0 && len(list) > 1 {
				if list[i].Start != (list[i-1].End + 1) {
					err = errors.New(fmt.Sprintf("[%s][%s] 发送帧字段占用字节不连续 %d~%d", frame.Name, row.FieldName, row.Start, row.End))
					return
				}
			}
		}
		if frame.SendType == 2 { // 条件触发参数判断
			conditions := make([]*FrameTriggerCondition, 0)
			err = json.Unmarshal([]byte(frame.TriggerCondition), &conditions)
			if err != nil {
				err = errors.New(fmt.Sprintf("触发条件参数解析失败 %s", err.Error()))
				return
			}
			if len(conditions) == 0 {
				err = errors.New(fmt.Sprintf("最少1个触发条件"))
				return
			}
			if len(conditions) > 3 {
				err = errors.New(fmt.Sprintf("最多3个触发条件"))
				return
			}
			switch frame.TriggerRelation {
			case "AND":
			case "OR":
			default:
				if len(conditions) > 1 {
					err = errors.New("触发关系参数无效 " + frame.TriggerRelation)
				}
				return
			}
			if frame.TriggerInterval <= 0 {
				frame.TriggerInterval = 5000 // 触发间隔默认5秒
			}
		}
	}

	// --- 做对比
	//addrs := GetAddrsFromFrameParam(frame.FrameItemParam)             // 已有变量需验证是否存在(不需要可以注释掉)
	//compareMap, err := GetVariablesMapByAddrs(frame.ProjectID, addrs) // 所有设置的变量信息
	//if err != nil {
	//	err = errors.New(fmt.Sprintf("ValidateFrameV2 获取对比变量失败 %s", err.Error()))
	//	return
	//}

	// --- 不做对比
	compareMap := make(map[string]*FrameItem)

	err = ValidateFrameParam(frame.FrameItemParam, frame, compareMap, false) // compareEnable 是否对比帧字段和变量的数据类型是否一致
	if err != nil {
		return errors.New(fmt.Sprintf("[%s]%s", frame.Name, err.Error()))
	}
	return
}

// CompareFrameParamSpecTypeWithVar 验证帧内容和所选择的变量的数据类型是否匹配
func CompareFrameParamSpecTypeWithVar(item *FrameItemParam, compareMap map[string]*FrameItem) (err error) {
	if item.Addr == "" {
		return
	}
	compareV, ok := compareMap[item.Addr]
	if !ok {
		err = errors.New(fmt.Sprintf("变量地址不存在: %s", item.Addr))
		return
	}
	if item.SpecType != compareV.SpecType {
		err = errors.New(fmt.Sprintf("%s 当前帧和所选变量数据类型不匹配~", item.FieldName))
		return
	}
	return
}

// ValidateFrameParam 验证帧内容每一行字段是否合规
func ValidateFrameParam(list []*FrameItemParam, frame *Frame, compareMap map[string]*FrameItem, compareEnable bool) (err error) {
	for index, item := range list {
		if !AllowBoolean[item.InputMethod] {
			err = errors.New(fmt.Sprintf("[index:%d]%s input_method 参数无效", index, item.FieldName))
			return
		}
		if item.Start < 0 || item.Start >= frame.Length {
			err = errors.New(fmt.Sprintf("[index:%d]%s 开始字节超出帧长度", index, item.FieldName))
			return
		}
		if item.End < 0 || item.End >= frame.Length {
			err = errors.New(fmt.Sprintf("[index:%d]%s 结束字节超出帧长度", index, item.FieldName))
			return
		}
		if item.Start > item.End {
			err = errors.New(fmt.Sprintf("[index:%d]%s 开始字节不可大于结束字节", index, item.FieldName))
			return
		}
		if !AllowBoolean[item.MultiByte] {
			err = errors.New(fmt.Sprintf("[index:%d]%s multi_byte 参数无效", index, item.FieldName))
			return
		}
		l := item.End - item.Start + 1

		if item.InputMethod == 1 {
			if item.RatioOperator == "" {
				item.RatioOperator = "*"
			}
			if item.Ratio == 0 {
				item.Ratio = 1.0
			}
			/*--- 数据部分验证 ---*/
			err = item.CheckDataPart(l)
			if err != nil {
				return errors.New(fmt.Sprintf("[index:%d]%s", index, err.Error()))
			}
			if compareEnable {
				err = CompareFrameParamSpecTypeWithVar(item, compareMap)
				if err != nil {
					return errors.New(fmt.Sprintf("[index:%d]%s", index, err.Error()))
				}
			}
		} else {
			/*--- 非数据部分验证 ---*/
			item.RawData = strings.Trim(item.RawData, " ")
			if item.Type == "校验" {
				if item.CheckMethod == "" {
					err = errors.New(fmt.Sprintf("[index:%d]%s 校验方法不能为空", index, item.FieldName))
					return
				}
				if !AllowCrcType[item.CheckMethod] {
					err = errors.New(fmt.Sprintf("[index:%d]%s 暂不支持此校验方法 %s", index, item.FieldName, item.CheckMethod))
					return
				}
				if item.CheckStart < 0 || item.CheckStart >= frame.Length {
					err = errors.New(fmt.Sprintf("[index:%d]%s 校验开始字节取值范围异常", index, item.FieldName))
					return
				}
				if item.CheckEnd < 0 || item.CheckEnd >= frame.Length {
					err = errors.New(fmt.Sprintf("[index:%d]%s 校验结束字节取值范围异常", index, item.FieldName))
					return
				}
				if item.CheckStart > item.CheckEnd {
					err = errors.New(fmt.Sprintf("[index:%d]%s 校验起始字节大于结束字节", index, item.FieldName))
					return
				}
			} else {
				if item.RawData == "" {
					err = errors.New(fmt.Sprintf("[index:%d]%s rawData值不能为空", index, item.FieldName))
					return
				}
				_, err = tools.ValidateHexAndLen(item.RawData, l)
				if err != nil {
					err = errors.New(fmt.Sprintf("[index:%d]%s rawData值格式错误 %s", index, item.FieldName, err.Error()))
					return
				}
			}

		}
	}
	return nil
}
func (item *FrameItemParam) CheckDataPart(l int) (err error) {
	if item.FieldName == "" {
		err = errors.New(fmt.Sprintf("%s 变量名不能为空", item.FieldName))
		return
	}
	if item.Endian != "" && !AllowEndian[item.Endian] {
		err = errors.New(fmt.Sprintf("%s endian 参数无效", item.FieldName))
		return
	}

	switch item.SpecType {
	case "int8":
		fallthrough
	case "uint8":
		if l != 1 {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
	case "int16":
		fallthrough
	case "uint16":
		if !(l >= 1 && l <= 2) {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
	case "int32":
		fallthrough
	case "uint32":
		if !(l >= 1 && l <= 4) {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
	case "int64":
		fallthrough
	case "uint64":
		if !(l >= 1 && l <= 8) {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
	case "float32":
		if l != 4 {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
	case "float64":
		if l != 8 {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
	case "bool":
		if l != 1 {
			err = errors.New(fmt.Sprintf("%s 占用字节异常", item.FieldName))
			return
		}
		if item.MultiByte == 1 {
			if item.BoolBits > 7 {
				err = errors.New(fmt.Sprintf("%s bool_bits 参数异常", item.FieldName))
				return
			}
		}
	default:
		err = errors.New(fmt.Sprintf("%s spec_type 参数无效", item.FieldName))
		return
	}
	return nil
}

func canFrameAddrHandle(mode, str string) (res string, err error) {
	if mode != "std" && mode != "ext" {
		err = errors.New("无效的扩展帧类型")
		return
	}
	if len(str) > 8 {
		err = errors.New("帧ID异常")
		return
	}
	if len(str) < 8 {
		padStr := strings.Repeat("0", 8-len(str))
		str = padStr + str
	}
	//fmt.Printf("帧ID: %s\n", str)
	b, err := tools.ValidateHexAndLen(str, 4)
	if err != nil {
		err = errors.New("帧ID格式错误, 必须为不超过4字节的16进制格式字符串")
		return
	}
	stdLen := uint32(2047)      // 0x7ff
	extLen := uint32(536870911) // 0x1fffffff
	l := tools.BytesToUint32(b, "big")
	if mode == "std" && l > stdLen {
		err = errors.New("标准帧长度不可超过 0x7ff")
		return
	}
	if mode == "ext" && l > extLen {
		err = errors.New("扩展帧长度不可超过 0x1fffffff")
		return
	}
	res = str
	return
}

type FrameTriggerCondition struct {
	FieldName string  `json:"field_name"`
	Addr      string  `json:"addr"`
	Compare   string  `json:"compare"`
	Value     float64 `json:"value"`
}

// IsTriggerConditionMatched 判断发送帧是否满足触发条件
func IsTriggerConditionMatched(frame *Frame) (res bool, err error) {
	conditions := make([]*FrameTriggerCondition, 0)
	err = json.Unmarshal([]byte(frame.TriggerCondition), &conditions)
	if err != nil {
		return
	}
	switch frame.TriggerRelation {
	case "AND":
		tmpRes := true
	loopAND:
		for _, row := range conditions {
			value := float64(0)
			variable, ok := VariablesMap.Get(row.Addr)
			if ok {
				value = variable.Value
			}
			//fmt.Printf("--- debug [%s - %s] (%.2f %s %.2f)\n", row.FieldName, row.Addr, value, row.Compare, row.Value)
			switch row.Compare {
			case ">":
				if !(value > row.Value) {
					tmpRes = false
					break loopAND
				}
			case ">=":
				if !(value >= row.Value) {
					tmpRes = false
					break loopAND
				}
			case "<":
				if !(value < row.Value) {
					tmpRes = false
					break loopAND
				}
			case "<=":
				if !(value <= row.Value) {
					tmpRes = false
					break loopAND
				}
			case "==":
				if !(value == row.Value) {
					tmpRes = false
					break loopAND
				}
			case "!=":
				if !(value != row.Value) {
					tmpRes = false
					break loopAND
				}
			default:
				err = errors.New("触发条件参数无效 " + row.Compare)
				return
			}
		}
		if tmpRes {
			res = true
			//fmt.Printf("--- debug 满足条件AND\n")
		} else {
			res = false
			//fmt.Printf("--- debug 不满足条件AND\n")
		}
	case "OR":
		tmpRes := false
	loopOR:
		for _, row := range conditions {
			value := float64(0)
			variable, ok := VariablesMap.Get(row.Addr)
			if ok {
				value = variable.Value
			}
			//fmt.Printf("--- debug [%s - %s] (%.2f %s %.2f)\n", row.FieldName, row.Addr, value, row.Compare, row.Value)
			switch row.Compare {
			case ">":
				if value > row.Value {
					tmpRes = true
					break loopOR
				}
			case ">=":
				if value >= row.Value {
					tmpRes = true
					break loopOR
				}
			case "<":
				if value < row.Value {
					tmpRes = true
					break loopOR
				}
			case "<=":
				if value <= row.Value {
					tmpRes = true
					break loopOR
				}
			case "==":
				if value == row.Value {
					tmpRes = true
					break loopOR
				}
			case "!=":
				if value != row.Value {
					tmpRes = true
					break loopOR
				}
			default:
				err = errors.New("触发条件参数无效 " + row.Compare)
				return
			}
		}
		if tmpRes {
			res = true
			//fmt.Printf("--- debug 满足条件OR\n")
		} else {
			res = false
			//fmt.Printf("--- debug 不满足条件OR\n")
		}
	default:
		err = errors.New("触发关系参数无效 " + frame.TriggerRelation)
		return
	}
	return
}
